//#include "GLee.h"
#include "renderer.h"
#include "common.h"
#include <GL/glu.h>
#include <iostream>
#include <QMouseEvent>
#include <QTimer>
#include <QFile>
#include <vector>
#include <cmath>
#include <assert.h>
#include "treedraw.h"
#include "GL/glext.h"
#include "GL/glut.h"
#include "GL/gl.h"
#include "treerender.h"
#include "shadowmap.h"
#include <assert.h>
#include <stdio.h>
#include <QMessageBox>
#include "scene3d.h"

using namespace std;
//Constructor, camera gets initialized here
#define GL_GLEXT_PROTOTYPES
const float cos60 = cos(M_PI/3);
const float sin60 = sin(M_PI/3);
Renderer::Renderer(QWidget *parent) : QGLWidget(QGLFormat(QGL::SampleBuffers | QGL::DoubleBuffer),parent) , mFPS(30.0f) {

    mIncrement = 0;
    mCamera = new Camera();

    mCamera->eye.x = 0.0f, mCamera->eye.y = 50.0f, mCamera->eye.z = 0.0f;
    mCamera->center.x = 0.0f, mCamera->center.y = 40.0f, mCamera->center.z = -50.0f;

    mCamera->up.x = 0.0f, mCamera->up.y = 1.0f, mCamera->up.z = 0.0f;
    mCamera->_angle = 45.0f, mCamera->_near = 1.0f, mCamera->_far = 10000.0f;


    QObject::connect(this, SIGNAL(_mouseDrag(QMouseEvent*)), this, SLOT(mouseDragEvent(QMouseEvent*)));
    setAutoBufferSwap(false);
    setFocusPolicy(Qt::StrongFocus);
    mTimer = new QTimer(this);
    connect(mTimer, SIGNAL(timeout()), this, SLOT(redraw()));
    mTimer->start(1000.0f / mFPS);
    left = false;
    right = false;
    up = false;
    down = false;
    pan_up = false;
    pan_down = false;
    modeInteract = false;

    this->curIntersect = float3(0,0,0);
    mQuadric = gluNewQuadric();
    for (int row = 0; row < TILING_FACTOR; row++){
        for (int col = 0; col < TILING_FACTOR; col++){
            patchLocations[row * TILING_FACTOR + col].x = (col-TILING_FACTOR/2)*TILE_LENGTH+TILE_LENGTH/2;
            patchLocations[row * TILING_FACTOR + col].y = 0.0;
            patchLocations[row * TILING_FACTOR + col].z = (row-TILING_FACTOR/2)*TILE_LENGTH+TILE_LENGTH/2;
        }
    }


    m_scene = new Scene3D();

    for (int i = 0; i < RANDOM_GRASS; i++){
        float x = (rand()%10000)/10000.0 * PLANE_SIZE*2-PLANE_SIZE;
        float z = (rand()%10000)/10000.0 * PLANE_SIZE*2-PLANE_SIZE;
        this->random_grass_pos[i] = float2(x,z);
    }
    lambda = .58;
    tropism = .3;
    aux_angle = .75;
    shed_thres = .2;
    term_weight = 3;
    aux_weight = 3;
    diameter_rate = 2.8;
    tlength = 7;
    alength = 7;
    alpha = 2;
}
//destructor
Renderer::~Renderer() {
    SAFE_DELETE(mTimer);
    SAFE_DELETE(mCamera);
    makeCurrent();


    gluDeleteQuadric(mQuadric);

    delete m_scene;

    foreach (QGLShaderProgram *sp, shader_programs)
        delete sp;
    foreach (QGLFramebufferObject *fo, framebuffer_objects)
        delete fo;
}

/**
    The main drawing method which will be called 30 frames per second.  Here
    is where your OpenGL drawing should occur.
**/
void Renderer::paintGL(){

      //some setup
        time = mIncrement++ / (float)mFPS;
        updateInteraction();
        updateCamera();
#ifdef ENVIRONMENT_VIEW
                framebuffer_objects["shadow_depth"]->bind();
glBindTexture(GL_TEXTURE_2D, this->grass_id);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_CULL_FACE);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_MODELVIEW);
        float increment_factor = PLANE_SIZE*2/TILING_FACTOR;
        for (int row = 0; row < TILING_FACTOR; row++){
            for (int col = 0; col < TILING_FACTOR;col++){

                glBegin(GL_QUADS);
                glTexCoord2d(0.0, 0.0);
                glVertex3f(-PLANE_SIZE+(col*increment_factor),0.0,-PLANE_SIZE+((row+1)*increment_factor));

                glTexCoord2d(0.0, 1.0);
                glVertex3f(-PLANE_SIZE+((col+1)*increment_factor),0.0,-PLANE_SIZE+((row+1)*increment_factor));

                glTexCoord2d(1.0, 1.0);
                glVertex3f(-PLANE_SIZE+((col+1)*increment_factor),0.0, -PLANE_SIZE+((row)*increment_factor));

                glTexCoord2d(1.0, 0.0);
                 glVertex3f(-PLANE_SIZE+(col*increment_factor),0.0, -PLANE_SIZE+((row)*increment_factor));
                glEnd();
            }
        }
        //TESTING PURPOSES
        glPushMatrix();
        glTranslatef(this->curIntersect.x, this->curIntersect.y, this->curIntersect.z);
    gluSphere(mQuadric, 10, 10, 10);
    glPopMatrix();


    TreeRender theRender;
     glBindTexture(GL_TEXTURE_2D, palm_texture);

    theRender.drawBranch(float3(0,0,-80), float3(0, 100, -80),0, 0,100, 20);
    glMatrixMode(GL_MODELVIEW);
    renderSkybox(mSkybox);

    glBindTexture(GL_TEXTURE_2D, this->grass_strip_id);
    setCurrentShader("texture_blend");
    int type = 0;
    shader_programs["texture_blend"]->setUniformValue("type", type);
     for (int row = 0; row < TILING_FACTOR; row++){
         for (int col = 0; col < TILING_FACTOR; col++){
             float distance = (mCamera->eye - patchLocations[row*TILING_FACTOR+col]).getMagnitude2();
             float xoff = GRASSW*cos(M_PI/3)+GRASSW-GRASSW/4+(int)distance/DENSITY_CONSTANT*GRASSW/2;
             float yoff = GRASSW*sin(M_PI/3)-GRASSW/4+(int)distance/DENSITY_CONSTANT*GRASSW/2;
              int width_elements = ceil(TILE_LENGTH/xoff);
              int height_elements = ceil(TILE_LENGTH/yoff);
             //render for current patch grass with certain density, try with regular density first
             for (int i = 0; i < height_elements; i++){
                 for (int j = 0; j < width_elements; j++){
                    renderGrass(float2((col-TILING_FACTOR/2)*TILE_LENGTH+j*xoff, (row-TILING_FACTOR/2)*TILE_LENGTH + i*yoff));
                    renderGrass(float2((col-TILING_FACTOR/2)*TILE_LENGTH+j*xoff+xoff/2, (row-TILING_FACTOR/2)*TILE_LENGTH + i*yoff+yoff/2));
                }
             }
         }
     }
    type = 1;
    shader_programs["texture_blend"]->setUniformValue("type", type);
     glBindTexture(GL_TEXTURE_2D, this->grass_disorted_id);
     for (int i = 0; i < RANDOM_GRASS; i++){
         renderGrass(this->random_grass_pos[i]);
     }
     releaseShader("texture_blend");

     framebuffer_objects["shadow_depth"]->release();
#endif
    //m_scene->draw();
    swapBuffers();
    glFlush();

}


void Renderer::applyValues(){
    m_scene->tree0->LAMBDA = lambda;
    m_scene->tree0->TROPISM = tropism;
    m_scene->tree0->AUX_ANGLE = aux_angle;
    m_scene->tree0->SHED_THRES = shed_thres;
    m_scene->tree0->TERM_WEIGHT = term_weight;
    m_scene->tree0->AUX_WEIGHT = aux_weight;
    m_scene->tree0->DIAMETER_RATE = diameter_rate;
    m_scene->tree0->TLENGTH = tlength;
    m_scene->tree0->ALENGTH = alength;
    m_scene->tree0->ALENGTH = alpha;
}


/**
  Setup OpenGL here.  You should specify your render settings within this method.
  This method is called once when Qt first retreives the OpenGL context during the
  creation of this widget.
  **/

void Renderer::initializeGL() {

     glEnable(GL_POLYGON_SMOOTH); //Enable smoothin
     glEnable(GL_DEPTH_TEST);
     glEnable(GL_COLOR_MATERIAL);
     glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); //set material properties which will be assigned by glColor
     glCullFace (GL_BACK);
     glEnable (GL_CULL_FACE);
     glShadeModel(GL_SMOOTH); //Smooth or flat shading model
     glPolygonMode(GL_FRONT, GL_FILL); //Shaded mode
     glPolygonMode(GL_BACK, GL_FILL);
     glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
     glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

     /* Setup Local Lighting *///Need a better lighting system
     GLfloat ambientLight[] = {0.1f, 0.1f, 0.1f, 1.0f};
     GLfloat diffuseLight[] = { 1.0f, 1.0f, 1.0, 1.0f };
     GLfloat specularLight[] = { 0.5f, 0.5f, 0.5f, 1.0f };
     GLfloat position[] = {0.0f, 0.0f, -20.0f, 1.0f };
     glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
     glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
     glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
     glLightfv(GL_LIGHT0, GL_POSITION, position);
     glClearColor(0.0,0.0, 0.0, 0.0); //Set the color to clear buffers to
     glEnable(GL_LIGHTING); //Enable lighting
     glEnable(GL_LIGHT0);
     srand(3);
     glEnable(GL_TEXTURE_2D);
     glEnableClientState(GL_TEXTURE_COORD_ARRAY);


    palm_texture = loadTexture(QString::fromStdString("../textures/palm_tree_bark_2.JPG"));
    grass_id = loadTexture(QString::fromStdString("../textures/grass_texture.jpg"));
    soil_id = loadTexture(QString::fromStdString("../textures/dirt.jpg"));
    grass_strip_id = loadTexture(QString::fromStdString("../textures/grass_strip.jpg"));
    grass_disorted_id = loadTexture(QString::fromStdString("../textures/distorted.jpg"));
    //TODO: find wrapping textures and meshes for leaves
    /*Setup skybox*/

   {
     mSkybox[0] = loadTexture(QString::fromStdString("../textures/posx.jpg"));
     mSkybox[1] = loadTexture(QString::fromStdString("../textures/negx.jpg"));
     mSkybox[2] = loadTexture(QString::fromStdString("../textures/posy.jpg"));
     mSkybox[3] = loadTexture(QString::fromStdString("../textures/negy.jpg"));
     mSkybox[4] = loadTexture(QString::fromStdString("../textures/posz.jpg"));
     mSkybox[5] = loadTexture(QString::fromStdString("../textures/negz.jpg"));
     QList<QFile *> fileList;
     fileList.append(new QFile("../textures/posx.jpg"));
     fileList.append(new QFile("../textures/negx.jpg"));
     fileList.append(new QFile("../textures/posy.jpg"));
     fileList.append(new QFile("../textures/negy.jpg"));
     fileList.append(new QFile("../textures/negz.jpg"));
     fileList.append(new QFile("../textures/posz.jpg"));

    if((mCubeID = loadCubeMap(fileList)) < 0) exit(0);
      glDisable(GL_TEXTURE_CUBE_MAP);

      foreach(QFile * f, fileList){
          delete f;
      }
  }

    loadShaders();

    load_framebuffers(this->width(), this->height());
    updateCamera();

}

void Renderer::renderGrass(float2 position){
    glDisable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindTexture(GL_TEXTURE_2D, grass_strip_id);
    glPushMatrix();
    glTranslatef(position.x, 0, position.y);

    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-GRASSW/2.0*cos60,GRASSH,GRASSW/2.0*sin60);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(GRASSW/2.0*cos60,GRASSH,-GRASSW/2.0*sin60);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(GRASSW/2.0*cos60,0.0f,-GRASSW/2.0*sin60);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-GRASSW/2.0*cos60,0.0f, GRASSW/2.0*sin60);
    glEnd();
    glPopMatrix();

    glPushMatrix();
    glTranslatef(position.x, 0, position.y);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-GRASSW/2.0*cos60,GRASSH,-GRASSW/2.0*sin60);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(GRASSW/2.0*cos60,GRASSH,GRASSW/2.0*sin60);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(GRASSW/2.0*cos60,0.0f,GRASSW/2.0*sin60);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-GRASSW/2.0*cos60,0.0f, -GRASSW/2.0*sin60);
    glEnd();
    glPopMatrix();

    glPushMatrix();
    glTranslatef(position.x, 0, position.y);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-GRASSW/2.0,GRASSH,0.0);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(GRASSW/2.0,GRASSH,0.0);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(GRASSW/2.0,0.0f,0.0);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-GRASSW/2.0,0.0f, 0.0);
    glEnd();
    glPopMatrix();
    glDisable(GL_BLEND);
    glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
    glEnable(GL_CULL_FACE);
}

/**
  Shader loading methods
  **/
/**********************************************************************************/
    void Renderer::loadShaders(){
        QString frag = "../224_Trees/color.frag";
        QString vert = "../224_Trees/color.vert";
        QString str = QString::fromStdString("color");
        shader_programs[str] = new QGLShaderProgram();
        shader_programs[str]->addShaderFromSourceFile(QGLShader::Fragment, frag);
        shader_programs[str]->addShaderFromSourceFile(QGLShader::Vertex, vert);
        linkShader(str);

        frag = "../224_Trees/texture_blend.frag";
        vert = "../224_Trees/texture_blend.vert";
         str = QString::fromStdString("texture_blend");
        shader_programs[str] = new QGLShaderProgram();
        shader_programs[str]->addShaderFromSourceFile(QGLShader::Fragment, frag);
        shader_programs[str]->addShaderFromSourceFile(QGLShader::Vertex, vert);
        linkShader(str);
    }
    /*Set uniforms here if you need to*/
    void Renderer::setCurrentShader(std::string shader){
        QString str = QString::fromStdString(shader);
        if (!shader.compare("color")){
            bindShader(str);
        }
        else if (!shader.compare("texture_blend")){
             GLfloat wind_intensity = (sin(time*M_PI)+1)/2.0;
             //cout << wind_intensity << endl;
            shader_programs["texture_blend"]->setUniformValue("tex", grass_strip_id);
            shader_programs["texture_blend"]->setUniformValue("tex2", this->grass_disorted_id);
           shader_programs["texture_blend"]->setUniformValue("wind", wind_intensity);
            bindShader(str);
        }
    }

/**
  Framebuffer methods
  **/
/***********************************************************************************************/
void Renderer::load_framebuffers(int w, int h){
    //Allocate the main framebuffer object for rendering the scene to
      //This needs a depth attachment.
    //Render buffer
    QGLFramebufferObjectFormat fboFormat;

    fboFormat.setAttachment(QGLFramebufferObject::Depth);

        fboFormat.setTextureTarget(GL_TEXTURE_2D);

        fboFormat.setInternalTextureFormat(GL_RGBA32F_ARB);

       framebuffer_objects["shadow_depth"] = new QGLFramebufferObject(w,h,GL_TEXTURE_2D);
       cout << "OH SHIT" << endl;
       framebuffer_objects["fbo_1"] = new QGLFramebufferObject(w,h,GL_TEXTURE_2D);
       framebuffer_objects["fbo_2"] = new QGLFramebufferObject(w,h,GL_TEXTURE_2D);
       framebuffer_objects["fbo_3"] = new QGLFramebufferObject(w,h,GL_TEXTURE_2D);
        //just use bind and release, and when you want the texture itself,texture()
  }


/**
  Texturing methods
  **/
/**********************************************************************************************************/
/*loads a texture with the name filename*/
GLuint Renderer::loadTexture(const QString &filename) {
    QImage image, texture;
    QFile file(filename);
    GLuint toReturn;
    if(!file.exists()){
        std::cout << "Failed to load texture: " << file.fileName().toStdString() << std::endl;
        return -1;
    }
    image.load(file.fileName());
   // image = image.mirrored(true, false);col
    texture = QGLWidget::convertToGLFormat(image);

    glGenTextures(1, &toReturn);
    glBindTexture(GL_TEXTURE_2D, toReturn);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, texture.width(), texture.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits());
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, texture.width(), texture.height(), GL_RGBA, GL_UNSIGNED_BYTE, texture.bits());

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    return toReturn;

}

/*Loads a cube map with all 6 textured sides*/
GLuint Renderer::loadCubeMap(QList<QFile *> files) {
    int i;
    GLuint id = -1;

    glEnable(GL_DEPTH_TEST);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    for(i = 0; i < 6; ++i) {
        QImage image, texture;
        image.load(files[i]->fileName());
        if (i != 2 && i != 3){
          image = image.mirrored(true, true);
       }
        texture = QGLWidget::convertToGLFormat(image);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 3, texture.width(), texture.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits());
    }

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);

    glEnable(GL_TEXTURE_CUBE_MAP);
    glEnable(GL_NORMALIZE);

    return id;
}

/*Renderes the cube map*/
void Renderer::renderSkybox(GLuint mSkybox[6]) {

    // Push a new matrix onto the stack for modelling transformations
    glPushMatrix();
    // Reset and transform the matrix.
     glTranslatef(0, 50, 0);

    glScalef(1000, 1000, 1000);
   // glTranslatef(mCamera->eye.x, mCamera->eye.y, mCamera->eye.z);
    // Enable/Disable features
    glPushAttrib(GL_ENABLE_BIT);
    glEnable(GL_TEXTURE_2D);
   // glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glDisable(GL_BLEND);
    // Just in case we set all vertices to white.
    glColor4f(1,1,1,1.0);


    // Render the right quad

   glBindTexture(GL_TEXTURE_2D, mSkybox[0]);
    glBegin(GL_QUADS);
    glTexCoord2f(1, 0); glVertex3f( 0.5f, -0.5f,  0.5f );
    glTexCoord2f(1, 1); glVertex3f( 0.5f,  0.5f,  0.5f );
    glTexCoord2f(0, 1); glVertex3f( 0.5f,  0.5f, -0.5f );
    glTexCoord2f(0, 0); glVertex3f( 0.5f, -0.5f, -0.5f );
    glEnd();

    // Render the left quad
    glBindTexture(GL_TEXTURE_2D, mSkybox[1]);
    glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex3f(  -0.5f, -0.5f,  0.5f );
    glTexCoord2f(1, 0); glVertex3f(  -0.5f, -0.5f, -0.5f );
    glTexCoord2f(1, 1); glVertex3f(  -0.5f,  0.5f, -0.5f );
    glTexCoord2f(0, 1); glVertex3f(  -0.5f,  0.5f,  0.5f );
    glEnd();

    // Render the top quad
    glBindTexture(GL_TEXTURE_2D, mSkybox[2]);
    glBegin(GL_QUADS);
        glTexCoord2f(1, 0); glVertex3f(  0.5f,  0.5f, -0.5f );
        glTexCoord2f(1, 1); glVertex3f(  0.5f,  0.5f,  0.5f );
        glTexCoord2f(0, 1); glVertex3f( -0.5f,  0.5f,  0.5f );
        glTexCoord2f(0, 0); glVertex3f( -0.5f,  0.5f, -0.5f );
    glEnd();

    // Render the bottom quad
       glBindTexture(GL_TEXTURE_2D, mSkybox[3]);
    glBegin(GL_QUADS);
        glTexCoord2f(0, 1); glVertex3f( -0.5f, -0.5f, -0.5f );
        glTexCoord2f(0, 0); glVertex3f( -0.5f, -0.5f,  0.5f );
        glTexCoord2f(1, 0); glVertex3f(  0.5f, -0.5f,  0.5f );
        glTexCoord2f(1, 1); glVertex3f(  0.5f, -0.5f, -0.5f );
    glEnd();

    // Render the back quad
    glBindTexture(GL_TEXTURE_2D, mSkybox[4]);
    glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f( -0.5f, -0.5f,  -0.5f );
    glTexCoord2f(1, 0); glVertex3f(  0.5f, -0.5f,  -0.5f );
    glTexCoord2f(1, 1); glVertex3f(  0.5f,  0.5f,  -0.5f );
        glTexCoord2f(0, 1); glVertex3f( -0.5f,  0.5f,  -0.5f );
    glEnd();

    //Render the front quad
    glBindTexture(GL_TEXTURE_2D, mSkybox[5]);
    glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f(  0.5f, -0.5f, 0.5f );
    glTexCoord2f(1, 0); glVertex3f( -0.5f, -0.5f, 0.5f );
    glTexCoord2f(1, 1); glVertex3f( -0.5f,  0.5f, 0.5f );
    glTexCoord2f(0, 1); glVertex3f(  0.5f,  0.5f, 0.5f );
    glEnd();


    // Restore enable bits and matrix
    glPopAttrib();
    glPopMatrix();
    glEnable(GL_LIGHTING);
    glEnable(GL_BLEND);
}


/**
  Methods for rendering to a textured quad
  **/
/****************************************************************************************************************/
/*Render a textured quad*/
void Renderer::textured_quad(int w,int h,bool flip) {
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    glBegin(GL_QUADS);
  glColor4f(0,0,0,0);
     glTexCoord2f(0.0f,flip ? 0.0f : 1.0f);
     glVertex3f(0.0f,h,-1.0);
     glTexCoord2f(1.0f,flip ? 0.0f : 1.0f);
     glVertex3f(w,h,-1.0);
     glTexCoord2f(1.0f,flip ? 1.0f : 0.0f);
     glVertex3f(w,0.0f,-1.0);
    glTexCoord2f(0.0f,flip ? 1.0f : 0.0f);
    glVertex3f(0.0f,0.0f, -1.0);
    glEnd();
}

/*Convert camera to orthographic mode*/
void Renderer::orthoMode(int w, int h){
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0,static_cast<float>(w),static_cast<float>(h),0.f,-1.f,1.f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
}

/**
  User interaction, update methods...
  **/
/****************************************************************************************************************/

   /* Resets the camera to fit the new viewport when the user resizes
    the window.  (Avoids object distortion)*/
void Renderer::resizeGL(int w, int h) {
    // Prevent a divide by zero, when window is too short
    // (you cant make a window of zero width).
    if(h == 0) h = 1;
    // Set the viewport to be the entire window (you probably want to keep this)
    glViewport(0, 0, w, h);
    updateCamera();
}

void Renderer::updateCamera() {
    float w = width(), h = height();
    float ratio = 1.0f * w / h;
    // Reset the coordinate system before modifying
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(mCamera->_angle, ratio, mCamera->_near, mCamera->_far);

    mCamera->nLook  = (mCamera->center - mCamera->eye).getNormalized();
    float3 tempup = mCamera->up.getNormalized();
    mCamera->nCross = -mCamera->nLook.cross(tempup).getNormalized();
    mCamera->nUp = mCamera->nLook.cross(mCamera->nCross).getNormalized();
    gluLookAt(mCamera->eye.x, mCamera->eye.y, mCamera->eye.z,
              mCamera->center.x, mCamera->center.y, mCamera->center.z,
              mCamera->up.x, mCamera->up.y, mCamera->up.z);
    glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void Renderer::mousePressEvent(QMouseEvent *event) {
    mLastMousePos.x = event->x(); mLastMousePos.y = event->y();
    if(event->button() == Qt::RightButton) { //Left click
        //This finds the world space coordinates of you mouse click
        //assuming the film plane is at z=1
        GLint viewport[4];
        GLdouble mvmatrix[16], projmatrix[16];
        GLint realy; /* OpenGL y coordinate position */
        GLdouble wx, wy, wz; /* returned world x, y, z coords     */
        glGetIntegerv (GL_VIEWPORT, viewport);
        glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
        glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
        realy = viewport[3] - (GLint) event->y() - 1;
        gluUnProject ((GLdouble) event->x(), (GLdouble) realy, 1.0,
                      mvmatrix, projmatrix, viewport, &wx, &wy, &wz);

        float3 ray = (float3(wx, wy, wz)-mCamera->eye).getNormalized();
        //noprmal is 0 1 0
        double d = -mCamera->eye.y/ray.y;
        this->curIntersect = mCamera->eye+d*ray;
    } else if (event->button() == Qt::RightButton) { //Right click

    } else if (event->button() == Qt::MidButton) { //Mid click

    }
}

void Renderer::mouseMoveEvent(QMouseEvent *event) {
    if(event->buttons() & Qt::LeftButton || event->buttons() & Qt::RightButton) //Mouse drag - don't modify this
        emit _mouseDrag(event);
    else if (event->buttons() & Qt::NoButton) {
    }
    mLastMousePos.x = event->x(); mLastMousePos.y = event->y();
}

void Renderer::mouseDragEvent(QMouseEvent *event) {
    if(event->buttons() & Qt::LeftButton) {
          if (!modeInteract){
             float dx = event->x() - mLastMousePos.x;
             float dy = event->y() - mLastMousePos.y;
             float3 look = mCamera->center - mCamera->eye;
             double r = look.getMagnitude();

             fflush(stdout);
             double theta = acos(look.y / r) + dy / 500.0f;
             double phi = atan2(look.z, look.x) + dx / 500.0f;
             if (theta > M_PI-.1) theta =M_PI-.1;
             if (theta < .1) theta = .1;

             float3 tempCenter;
             tempCenter.x = r * sin(theta) * cos(phi) + mCamera->eye.x;
             tempCenter.z = r * sin(theta) * sin(phi) + mCamera->eye.z;
             tempCenter.y = r * cos(theta) + mCamera->eye.y;
             look = (tempCenter - mCamera->eye).getNormalized();
             float3 axis(0,-1,0);
#ifdef LIMIT_VIEW
             if (look.dot(axis) <= LOOK_LIMIT){
                mCamera->center = tempCenter;
             }
             else{
                 theta -= dy / 500.0f;
                 mCamera->center.x = r * sin(theta) * cos(phi) + mCamera->eye.x;
                 mCamera->center.z = r * sin(theta) * sin(phi) + mCamera->eye.z;
                 mCamera->center.y = r * cos(theta) + mCamera->eye.y;
             }
#else
         mCamera->center = tempCenter;
#endif
         }
          else{
              //perform Interaction here
          }
     }
}

void Renderer::wheelEvent(QWheelEvent *event) {
    mCamera->eye += (mCamera->center - mCamera->eye).getNormalized() / 100.0f * event->delta();
}

void Renderer::keyPressEvent(QKeyEvent *event) {

    if(event->key() == Qt::Key_Q) {
        pan_up = true;
    }
    else if (event->key() == Qt::Key_E){
        pan_down = true;
    }
    else if (event->key() == Qt::Key_W) {
        up = true;
    }
    else if (event->key() == Qt::Key_A) {
        left = true;
    }
    else if (event->key() == Qt::Key_D) {
        right = true;
    }
    else if (event->key() == Qt::Key_S) {
        down = true;
    }

}

void Renderer::keyReleaseEvent(QKeyEvent *event) {

    if(event->key() == Qt::Key_Q) {
        pan_up = false;
    }
    else if(event->key() == Qt::Key_E){
        pan_down= false;
    }
    else if (event->key() == Qt::Key_W) {
        up = false;
    }
    else if (event->key() == Qt::Key_A) {
        left = false;
    }
    else if (event->key() == Qt::Key_D) {
        right = false;
    }
    else if (event->key() == Qt::Key_S) {
        down = false;
    }
}

void Renderer::updateInteraction(){

    if (up) {
       mCamera->eye += 2*float3(mCamera->nLook.x, 0.0, mCamera->nLook.z);
       mCamera->center += 2*float3(mCamera->nLook.x, 0.0, mCamera->nLook.z);
   }
    if (left) {
       mCamera->eye += 2*float3(mCamera->nCross.x,0.0, mCamera->nCross.z);
       mCamera->center += 2*float3(mCamera->nCross.x,0.0, mCamera->nCross.z);
    }
    if (right) {
       mCamera->eye -= 2*float3(mCamera->nCross.x,0.0, mCamera->nCross.z);
       mCamera->center -= 2*float3(mCamera->nCross.x,0.0, mCamera->nCross.z);
    }
    if (down) {
       mCamera->eye -= 2*float3(mCamera->nLook.x, 0.0, mCamera->nLook.z);
       mCamera->center -= 2*float3(mCamera->nLook.x, 0.0, mCamera->nLook.z);
    }
    if (pan_up){
#ifdef LIMIT_VIEW
        if (mCamera->eye.y + mCamera->nUp.y <= VERT_LIMIT_T){
            mCamera->eye += mCamera->nUp;
            mCamera->center += mCamera->nUp;
        }
#else
        mCamera->eye += mCamera->nUp;
        mCamera->center += mCamera->nUp;
#endif
    }
    if(pan_down){
#ifdef LIMIT_VIEW
        if (mCamera->eye.y-mCamera->nUp.y >= VERT_LIMIT_B){
            mCamera->eye -= mCamera->nUp;
            mCamera->center -= mCamera->nUp;
        }
#else
        mCamera->eye -= mCamera->nUp;
        mCamera->center -= mCamera->nUp;
#endif
    }
}

void Renderer::redraw() {
    repaint();
}
